﻿using Loong.Admin.Security.Cryptography;
using Loong.Admin.Text.Normalization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Domain.Services;

namespace Loong.Admin.Users
{
    public class UserManager : DomainService
    {
        public UserOptions UserOptions { get; }

        public LockoutOptions LockoutOptions { get; }

        public IPasswordHasher PasswordHasher { get; set; }

        public IList<IPasswordValidator> PasswordValidators { get; } = [];

        public IList<IUserValidator> UserValidators { get; } = [];

        public ILookupNormalizer KeyNormalizer { get; set; }

        private readonly IServiceProvider _services;

        public UserManager(
            IOptions<UserOptions> userOptionsAccessor,
            IOptions<LockoutOptions> lockoutOptionsAccessor,
            IPasswordHasher passwordHasher,
            IEnumerable<IPasswordValidator> passwordValidators,
            IEnumerable<IUserValidator> userValidators,
            ILookupNormalizer keyNormalizer,
            IServiceProvider services)
        {
            UserOptions = userOptionsAccessor.Value;
            LockoutOptions = lockoutOptionsAccessor.Value;
            PasswordHasher = passwordHasher;
            KeyNormalizer = keyNormalizer;

            foreach (var v in passwordValidators)
            {
                PasswordValidators.Add(v);
            }

            foreach (var v in userValidators)
            {
                UserValidators.Add(v);
            }

            _services = services;
        }

        public async Task CreateAsync(User user, string password)
        {
            Check.NotNull(user, nameof(user));
            Check.NotNull(password, nameof(password));

            await UpdatePasswordHashAsync(user, password);

            await CreateAsync(user);
        }

        public async Task CreateAsync(User user)
        {
            user.SetSecurityStamp();
            await ValidateUserAsync(user);
            if (LockoutOptions.AllowedForNewUsers)
            {
                user.LockoutEnabled = true;
            }
            UpdateNormalizedUserName(user);
            UpdateNormalizedEmail(user);
        }

        public async Task UpdatePasswordHashAsync(User user, string? newPassword, bool validatePassword = true)
        {
            if (validatePassword)
            {
                await ValidatePasswordAsync(newPassword);
            }

            var hash = newPassword != null ? PasswordHasher.HashPassword(newPassword) : null;
            user.PasswordHash = hash;
            user.SetSecurityStamp();
        }

        protected async Task ValidatePasswordAsync(string? password)
        {
            foreach (var v in PasswordValidators)
            {
                await v.ValidateAsync(password);
            }
        }

        protected async Task ValidateUserAsync(User user)
        {
            if (user.SecurityStamp == null)
            {
                throw new InvalidOperationException("SecurityStamp不能为空");
            }

            foreach (var v in UserValidators)
            {
                await v.ValidateAsync(user);
            }
        }

        public void UpdateNormalizedUserName(User user)
        {
            var normalizedUserName = KeyNormalizer.Normalize(user.UserName);
            normalizedUserName = ProtectPersonalData(normalizedUserName);
            user.NormalizedUserName = normalizedUserName!;
        }

        public void UpdateNormalizedEmail(User user)
        {
            user.NormalizedEmail = ProtectPersonalData(KeyNormalizer.Normalize(user.Email))!;
        }

        private string? ProtectPersonalData(string? data)
        {
            if (!UserOptions.ProtectPersonalData) return data;

            //var keyRing = _services.GetRequiredService<ILookupProtectorKeyRing>();
            //var protector = _services.GetRequiredService<ILookupProtector>();
            //return protector.Protect(keyRing.CurrentKeyId, data);
            return data;
        }
    }
}
